//------------------------------------------------------------------------------
// BinaryEdit.cpp
//------------------------------------------------------------------------------
// File provided for Microsoft Shared Source.
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE.
//------------------------------------------------------------------------------
//
// Binary edit control
//
//------------------------------------------------------------------------------

#include "pch.h"
#pragma warning(push)
#pragma warning(disable:4995)
#include <atlwin.h>
#pragma warning(pop)
#include <atltypes.h>
#include <uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
#include <StackString.h>
#include <Utils.h>
#include "BinaryEdit.h"
#include "BinaryEditIds.h"

#pragma comment (lib, "uxtheme.lib")
#pragma comment (lib, "comctl32.lib")

//------------------------------------------------------------------------------
// CBinaryEdit
//------------------------------------------------------------------------------

#define IDC_TOOLBAR 100

const UINT c_cxDivider = 1;         // The width of a divider line if not provided by the theme
const UINT c_cxGridPadding = 5;
const UINT c_cxHexCellPadding = 3;
const UINT c_cxCharCellPadding = 1;
const UINT c_cyMinHeader = 19;

typedef CStackString<3> CByteString;

class CBinaryEdit :
    public CWindowImpl<CBinaryEdit>
{
    friend class CUndoManager;
public:
    CBinaryEdit() : _dwState(0), _hthmHeader(NULL), _hthmListview(NULL), _fThemed(FALSE), _fBufferedPaint(FALSE), _hfntDefault(NULL),
                    _hfnt(NULL), _fAlternateRowColor(FALSE), _hct(HCT_BYTE), _hcdt(HCDT_AUTO), _cMinHexCols(4), _cbHexCell(sizeof(BYTE)),
                    _cyHeader(0), _cyRow(0), _cxRowHeader(0), _cxHexCell(0), _cxCharCell(0), _cHexCols(0), _cbRow(0),
                    _cRows(0), _iRowOrigin(0), _iMaxRowOrigin(0), _iCaretPos(0), _iSelStart(-1), _iSelEnd(-1),
                    _hmnuTrack(NULL)
    {
        ZeroMemory(&_tm, sizeof(_tm));
        _sizeDivider.SetSize(0,0);
    }

    static HRESULT s_CreateInstance(HWND hwndOwner, DWORD dwStyle, RECT const &rc, UINT uiId, __out HWND *phwnd);

    DECLARE_WND_CLASS_EX(L"BinaryEdit", 0, 0) 

    BEGIN_MSG_MAP(CBinaryEdit)
        MESSAGE_HANDLER(WM_NCCREATE, _OnNcCreate)
        MESSAGE_HANDLER(WM_NCDESTROY, _OnNcDestroy)
        MESSAGE_HANDLER(WM_DESTROY, _OnDestroy);
        MESSAGE_HANDLER(WM_SETFOCUS, _OnSetFocus)
        MESSAGE_HANDLER(WM_KILLFOCUS, _OnKillFocus)
        MESSAGE_HANDLER(WM_STYLECHANGING, _OnStyleChanging)
        MESSAGE_HANDLER(WM_SETFONT, _OnSetFont)
        MESSAGE_HANDLER(WM_SIZE, _OnSize)
        MESSAGE_HANDLER(WM_ERASEBKGND, _OnEraseBkgnd)
        MESSAGE_HANDLER(WM_NCPAINT, _OnNcPaint)
        MESSAGE_HANDLER(WM_PRINTCLIENT, _OnPrintClient)
        MESSAGE_HANDLER(WM_PAINT, _OnPaint)
        MESSAGE_HANDLER(WM_VSCROLL, _OnVScroll)
        MESSAGE_HANDLER(WM_MOUSEWHEEL, _OnMouseWheel)
        MESSAGE_HANDLER(WM_LBUTTONDOWN, _OnLButtonDown)
        MESSAGE_HANDLER(WM_MOUSEMOVE, _OnMouseMove)
        MESSAGE_HANDLER(WM_LBUTTONUP, _OnLButtonUp)
        MESSAGE_HANDLER(WM_CAPTURECHANGED, _OnCaptureChanged)
        MESSAGE_HANDLER(WM_SETCURSOR, _OnSetCursor)
        MESSAGE_HANDLER(WM_GETDLGCODE, _OnGetDlgCode)
        MESSAGE_HANDLER(WM_KEYDOWN, _OnKeyDown)
        MESSAGE_HANDLER(WM_CHAR, _OnChar)
        MESSAGE_HANDLER(WM_MENUSELECT, _OnMenuSelect)
        MESSAGE_HANDLER(WM_ENTERIDLE, _OnEnterIdle)
        MESSAGE_HANDLER(BEM_SETHEXCELLTYPE, _OnSetHexCellType)
        MESSAGE_HANDLER(BEM_GETHEXCELLTYPE, _OnGetHexCellType)
        MESSAGE_HANDLER(BEM_SETHEXCELLDIVIDERTYPE, _OnSetHexCellDividerType)
        MESSAGE_HANDLER(BEM_GETHEXCELLDIVIDERTYPE, _OnGetHexCellDividerType)
        MESSAGE_HANDLER(BEM_SETALTERNATEROWCOLOR, _OnSetAlternateRowColor)
        MESSAGE_HANDLER(BEM_GETALTERNATEROWCOLOR, _OnGetAlternateRowColor)
        MESSAGE_HANDLER(BEM_SETDATA, _OnSetData)
        MESSAGE_HANDLER(BEM_GETDATASIZE, _OnGetDataSize)
        MESSAGE_HANDLER(BEM_GETDATA, _OnGetData)
        COMMAND_HANDLER(IDI_ALTERNATEROWCOLOR, BN_CLICKED, _OnAlternateRowColor);
        NOTIFY_HANDLER(IDC_TOOLBAR, TBN_DROPDOWN, _OnTBNDropDown)
    END_MSG_MAP()

    virtual void OnFinalMessage(HWND hwnd);

private:
    enum BINARYEDITSTATE
    {
        BES_CHARGRIDFOCUS =     0x0001,
        BES_OVERWRITE =         0x0002,
        BES_FOCUSONLOWNIBBLE =  0x0004,
        BES_CAPTURE =           0x0008,
        BES_CARETVISIBLE =      0x0010,
    };

    enum BINARYEDITPART
    {
        BEP_TOOLBAR = 0,
        BEP_HEADER,
        BEP_ROWHEADER,
        BEP_BODY,
        BEP_HEXGRID,
        BEP_VIRTUALHEXGRID,
        BEP_CHARGRID,
        BEP_VIRTUALCHARGRID,
    };

    struct DRAWCONTEXT
    {
        ~DRAWCONTEXT()
        {
            if (hbrRowHdrEven)
            {
                DeleteObject(hbrRowHdrEven);
            }
            if (hbrRowHdrOdd)
            {
                DeleteObject(hbrRowHdrOdd);
            }
            if (hbrRowOdd)
            {
                DeleteObject(hbrRowOdd);
            }
            if (hbrDivider)
            {
                DeleteObject(hbrDivider);
            }
        }

        HBRUSH hbrRowHdrEven;
        HBRUSH hbrRowHdrOdd;
        HBRUSH hbrRowOdd;
        HBRUSH hbrDivider;
    };

    enum HITTESTTYPE
    {
        HTT_NORMAL,
        HTT_FORINSERTION,
        HTT_FORDRAG,
    };

    enum HITTYPE
    {
        HT_NOWHERE,
        HT_ROWHEADER,
        HT_HEXCELL,
        HT_CHARCELL,
    };

    struct HITTEST
    {
        HITTYPE ht;
        union
        {
            struct
            {
                size_t iByte;
                BOOL fLowNibble;
            };
            size_t iRow;
        };
    };

    enum SETCARETPOSFLAGS
    {
        SCP_SHOW =              0x0001,
        SCP_HIDE =              0x0002,
        SCP_ADJUSTSELECTION =   0x0004,
        SCP_ENSUREVISIBLE =     0x0008,
        SCP_LOWNIBBLE =         0x0010,
        SCP_NOMOVE =            0x0020,
    };

    enum KEYMODIFIER
    {
        KM_SHIFT =      0x0001,
        KM_CONTROL =    0x0002,
        KM_ALT =        0x0004,
    };

    HRESULT _CreateDefaultFont();
    HRESULT _CreateToolbar();
    void _DestroyToolbar();
    void _UpdateToolbarButtons();
    void _UpdateThemeInfo();
    void _CloseThemeData();
    void _OnFontChange();
    void _RecalcLayout();
    UINT _CalcPotentialColumnsFromWidth(UINT cx);
    void _UpdateScrollInfo();
    UINT _GetColumnDividerCount();
    UINT _RowsPerPage();
    UINT _IntegralRowsPerPage();
    HRESULT _BinaryToText(BYTE b, __out PWSTR pszText, DWORD cch);
    void _ScrollContents(int cLines);
    void _EnsureVisible(size_t iByte);
    void _CalcPartRect(BINARYEDITPART bep, __out CRect *prc);
    void _CalcByteCoordinates(size_t iByte, BOOL fCharGrid, __out size_t *piRow, __out size_t *piCol);
    void _CalcCaretPos(size_t iByte, __out POINT *pt);
    void _SetCaretPos(size_t iByte, DWORD dwFlags);
    void _AdjustCaretPosition(UINT uiDirection, DWORD dwKeyModifiers);
    BOOL _IsByteVisible(size_t iByte);
    BOOL _SetHexCellType(HEXCELLTYPE hct);
    BOOL _SetHexCellDividerType(HEXCELLDIVIDERTYPE hcdt);
    BOOL _IsValidCharacter(WCHAR wc, __out BYTE *pb);
    BOOL _IsByteSelected(size_t iIndex);
    void _HitTest(const CPoint &pt, HITTESTTYPE htt, __out HITTEST *pht);
    void _HitTestHexCell(const CPoint &pt, const CRect &rcVHexGrid, HITTESTTYPE htt, __inout HITTEST *pht);
    DWORD _GetKeyModifiers();
    void _DeleteText(UINT uiKey);
    BOOL _HasSelection();
    size_t _GetDataActionIndex();
    HRESULT _ModifyData(size_t iIndex, size_t cReplace, WORD *pwNew, size_t cNew);

    HRESULT _CreateDrawContext(__out DRAWCONTEXT *pdc);
    HBRUSH _CreateModifiedSysColorBrush(int nIndex, int nNumerator, int nDenominator);
    void _Draw(HDC hdc, const CRect &rcBounds, const CRect &rcInvalid);
    void _DrawHeader(HDC hdc, const CRect &rc);
    void _DrawRow(HDC hdc, const DRAWCONTEXT &dc, size_t iRow, const CRect &rc);

    // Message handlers
    LRESULT _OnNcCreate(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnNcDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetFocus(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnKillFocus(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnStyleChanging(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetFont(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnNcPaint(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnEraseBkgnd(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnPrintClient(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnPaint(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnVScroll(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnMouseWheel(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnLButtonDown(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnMouseMove(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnLButtonUp(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnCaptureChanged(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetCursor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetDlgCode(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnKeyDown(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnChar(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnMenuSelect(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnEnterIdle(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetHexCellType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetHexCellType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetHexCellDividerType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetHexCellDividerType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetAlternateRowColor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetAlternateRowColor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnSetData(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetDataSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);
    LRESULT _OnGetData(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled);

    // Command handlers
    LRESULT _OnAlternateRowColor(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled);

    // Notification handlers
    LRESULT _OnTBNDropDown(int nId, NMHDR *pnmhdr, BOOL &fHandled);

private:
    CWindow _wndToolbar;

    DWORD _dwState;
    BOOL _fThemed;
    BOOL _fBufferedPaint;
    HTHEME _hthmHeader;
    HTHEME _hthmListview;
    HFONT _hfntDefault;
    HFONT _hfnt;
    BOOL _fAlternateRowColor;

    HEXCELLTYPE _hct;
    HEXCELLDIVIDERTYPE _hcdt;
    UINT _cMinHexCols;
    UINT _cbHexCell;
    TEXTMETRIC _tm;

    UINT _cyHeader;
    UINT _cyRow;
    UINT _cxRowHeader;
    UINT _cxHexCell;
    UINT _cxCharCell;
    CSize _sizeDivider;

    UINT _cHexCols;
    UINT _cbRow;
    UINT _cRows;
    size_t _iRowOrigin;
    size_t _iMaxRowOrigin;

    size_t _iCaretPos;
    size_t _iSelStart;
    size_t _iSelEnd;

    HMENU _hmnuTrack;
    UINT _uiTBCtrlIdTrack;
    UINT _uiTrack;
    CRect _rcDrag;

    CAtlArray<WORD> _rgData;
};

HRESULT CBinaryEdit::s_CreateInstance(HWND hwndOwner, DWORD dwStyle, RECT const &rc, UINT uiId, __out HWND *phwnd)
{
    CBinaryEdit *pbe = new CBinaryEdit();
    HRESULT hr = pbe ? S_OK : E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
    {
        CAutoPtr<CBinaryEdit> spbe;
        spbe.Attach(pbe);
        DWORD dwExStyle = 0;
        if (dwStyle & WS_BORDER)
        {
            dwStyle &= ~WS_BORDER;
            dwExStyle |= WS_EX_CLIENTEDGE;
        }
        HWND hwnd = spbe->Create(hwndOwner, const_cast<RECT &>(rc), NULL, dwStyle | WS_CLIPCHILDREN, dwExStyle, uiId);
        hr = hwnd ? S_OK : E_FAIL;
        if (SUCCEEDED(hr))
        {
            *phwnd = hwnd;
            spbe.Detach();
        }
    }
    return hr;
}

STDAPI CreateBinaryEdit(HWND hwndOwner, DWORD dwStyle, RECT const &rc, UINT uiId, __out HWND *phwnd)
{
    return CBinaryEdit::s_CreateInstance(hwndOwner, dwStyle, rc, uiId, phwnd);
}

void CBinaryEdit::OnFinalMessage(HWND hwnd)
{
    delete this;
}

void CBinaryEdit::_CloseThemeData()
{
    if (_hthmHeader)
    {
        CloseThemeData(_hthmHeader);
        _hthmHeader = NULL;
    }
    if (_hthmListview)
    {
        CloseThemeData(_hthmListview);
        _hthmListview = NULL;
    }
    _fThemed = FALSE;
}

void CBinaryEdit::_UpdateThemeInfo()
{
    _CloseThemeData();
    _hthmHeader = OpenThemeData(NULL, L"Header");
    if (_hthmHeader)
    {
        _hthmListview = OpenThemeData(NULL, L"Listview");
        if (_hthmListview)
        {
            _fThemed = TRUE;
        }
        else
        {
            _CloseThemeData();
        }
    }

    if (!_fThemed || FAILED(GetThemePartSize(_hthmListview, NULL, LVP_COLUMNDETAIL, 0, NULL, TS_TRUE, &_sizeDivider)))
    {
        _sizeDivider.cx = c_cxDivider;
    }
}

HRESULT CBinaryEdit::_CreateDefaultFont()
{
    NONCLIENTMETRICS ncm = {sizeof(ncm)};
    HRESULT hr = SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0) ? S_OK : AtlHresultFromLastError();
    if (SUCCEEDED(hr))
    {
        LOGFONT lf = {};
        lf.lfHeight = ncm.lfMenuFont.lfHeight;
        lf.lfWeight = ncm.lfMenuFont.lfWeight;
        hr = StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), L"Courier New");
        if (SUCCEEDED(hr))
        {
            HFONT hfnt = CreateFontIndirect(&lf);
            hr = hfnt ? S_OK : AtlHresultFromLastError();
            if (SUCCEEDED(hr))
            {
                _hfntDefault = hfnt;
            }
        }
    }
    return hr;
}

void CBinaryEdit::_OnFontChange()
{
    HDC hdc = GetDC();
    HRESULT hr = hdc ? S_OK : AtlHresultFromLastError();
    if (SUCCEEDED(hr))
    {
        HGDIOBJ hfntOld = SelectObject(hdc, _hfnt);
        TEXTMETRIC tm = {};
        hr = GetTextMetrics(hdc, &tm) ? S_OK : AtlHresultFromLastError();
        if (SUCCEEDED(hr))
        {
            _tm = tm;
            _RecalcLayout();
        }
        SelectObject(hdc, hfntOld);
        ReleaseDC(hdc);
    }
}

UINT CBinaryEdit::_RowsPerPage()
{
    CRect rc;
    _CalcPartRect(BEP_BODY, &rc);
    div_t divRes = div((int)rc.Height(), (int)_cyRow);
    UINT cRows = divRes.quot + (divRes.rem ? 1 : 0);
    return cRows;
}

UINT CBinaryEdit::_IntegralRowsPerPage()
{
    CRect rc;
    _CalcPartRect(BEP_BODY, &rc);
    return (rc.Height() / _cyRow);
}

void CBinaryEdit::_UpdateScrollInfo()
{
    SCROLLINFO si = {sizeof(si)};
    si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
    si.nMax = _cRows - 1;
    si.nPage = _IntegralRowsPerPage();
    si.nPos = (int)_iRowOrigin;
    SetScrollInfo(SB_VERT, &si);
}

UINT CBinaryEdit::_CalcPotentialColumnsFromWidth(UINT cx)
{
    UINT cCols =  (((cx - (_cxRowHeader + (4 * c_cxGridPadding))) /
           ((_cMinHexCols * _cxHexCell) + (_cMinHexCols * _cbHexCell * _cxCharCell)))) * _cMinHexCols;

    if (cCols < _cMinHexCols)
    {
        cCols = _cMinHexCols;
    }
    return cCols;
}

BOOL CBinaryEdit::_IsByteVisible(size_t iByte)
{
    size_t iRow;
    size_t iCol;
    _CalcByteCoordinates(iByte, FALSE, &iRow, &iCol);
    return (iRow >= _iRowOrigin && iRow < _iRowOrigin + _RowsPerPage());
}

void CBinaryEdit::_RecalcLayout()
{
    // Set up the font specific values
    _cyHeader = max(_tm.tmHeight, c_cyMinHeader);
    _cyRow = _tm.tmHeight;
    _cxRowHeader = _tm.tmAveCharWidth * 11;
    _cxHexCell = (_tm.tmAveCharWidth * (_cbHexCell * 2)) + (c_cxHexCellPadding * 2);
    _cxCharCell = _tm.tmAveCharWidth + (c_cxCharCellPadding * 2);

    // Figure out how many columns to display
    CRect rc;
    GetClientRect(&rc);

    _cHexCols = _CalcPotentialColumnsFromWidth(rc.Width());

    BOOL fAbortLayout = FALSE;
    SCROLLBARINFO sbi = {sizeof(sbi)};
    if (GetScrollBarInfo(m_hWnd, OBJID_VSCROLL, &sbi))
    {
        // If a vertical scrollbar is currently visible, determine if removing it would
        // cause a reflow of some columns
        if (!(sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE))
        {
            UINT cCols = _CalcPotentialColumnsFromWidth(rc.Width() + (sbi.rcScrollBar.right - sbi.rcScrollBar.left));
            UINT cRows = (UINT)_rgData.GetCount() / cCols + 1;
            if (cCols > _cHexCols && cRows <= _IntegralRowsPerPage())
            {
                SCROLLINFO si = {sizeof(si)};
                si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
                SetScrollInfo(SB_VERT, &si);
                fAbortLayout = TRUE;
                _iRowOrigin = 0;
            }
        }
    }

    if (!fAbortLayout)
    {
        _SetCaretPos(0, SCP_HIDE | SCP_NOMOVE);

        _cbRow = _cHexCols * _cbHexCell;
        _cRows = (UINT)(_rgData.GetCount() / (_cHexCols * _cbHexCell)) + 1;
        _iMaxRowOrigin = _cRows - _IntegralRowsPerPage();

        _UpdateScrollInfo();

        _SetCaretPos(0, SCP_SHOW | SCP_NOMOVE);
    }
}

UINT CBinaryEdit::_GetColumnDividerCount()
{
    return (_cHexCols / _cMinHexCols) - 1;
}

void CBinaryEdit::_UpdateToolbarButtons()
{
    _wndToolbar.SendMessage(TB_CHECKBUTTON, IDI_ALTERNATEROWCOLOR, _fAlternateRowColor);
}

HRESULT CBinaryEdit::_CreateToolbar()
{
    CRect rcToolbar;
    _CalcPartRect(BEP_TOOLBAR, &rcToolbar);
    HRESULT hr = _wndToolbar.Create(TOOLBARCLASSNAME, m_hWnd, rcToolbar, NULL, CCS_NODIVIDER | CCS_NORESIZE | CCS_NOPARENTALIGN | TBSTYLE_LIST | TBSTYLE_TOOLTIPS | TBSTYLE_TRANSPARENT | WS_VISIBLE | WS_CHILD, 0, IDC_TOOLBAR) ? S_OK : AtlHresultFromLastError();
    if (SUCCEEDED(hr))
    {
        HIMAGELIST himl = ImageList_Create(10, 11, ILC_COLOR32, 0, 0);
        hr = himl ? S_OK : AtlHresultFromLastError();
        if (SUCCEEDED(hr))
        {
            static const UINT rgIconIds[] = 
            {
                IDI_HEXCOLUMNSIZE,
                IDI_COLUMNDIVIDERS,
                IDI_ALTERNATEROWCOLOR,
            };

            for (UINT i = 0; i < ARRAYSIZE(rgIconIds) && SUCCEEDED(hr); i++)
            {
                HICON hicn = (HICON)LoadImage(_AtlBaseModule.GetModuleInstance(), MAKEINTRESOURCE(rgIconIds[i]), IMAGE_ICON, 10, 11, 0);
                hr = hicn ? S_OK : AtlHresultFromLastError();
                if (SUCCEEDED(hr))
                {
                    hr = (ImageList_AddIcon(himl, hicn) != -1) ? S_OK : E_OUTOFMEMORY;
                    DestroyIcon(hicn);
                }
            }

            if (SUCCEEDED(hr))
            {
                _wndToolbar.SendMessage(TB_SETINDENT, 2, 0);
                _wndToolbar.SendMessage(TB_SETIMAGELIST, 0, (LPARAM)himl);
                _wndToolbar.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS | TBSTYLE_EX_DOUBLEBUFFER);
                _wndToolbar.SendMessage(TB_ADDSTRING, (WPARAM)_AtlBaseModule.GetModuleInstance(), IDS_TOOLBARTIPS);

                static TBBUTTON s_rgtbb[] = 
                {
                    { 0, IDI_HEXCOLUMNSIZE, TBSTATE_ENABLED, BTNS_DROPDOWN, {}, NULL, 0 },
                    { 1, IDI_COLUMNDIVIDERS, TBSTATE_ENABLED, BTNS_DROPDOWN, {}, NULL, 1 },
                    { 2, -1, TBSTATE_ENABLED, BTNS_SEP, {}, NULL, NULL },
                    { 2, IDI_ALTERNATEROWCOLOR, TBSTATE_ENABLED, BTNS_CHECK, {}, NULL, 2 },
                };
                hr = _wndToolbar.SendMessage(TB_ADDBUTTONS, ARRAYSIZE(s_rgtbb), (LPARAM)s_rgtbb) ? S_OK : E_FAIL;
                if (SUCCEEDED(hr))
                {
                    _UpdateToolbarButtons();
                }
            }
        }
    }
    return hr;
}

LRESULT CBinaryEdit::_OnNcCreate(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    _UpdateThemeInfo();

    HRESULT hr = _CreateDefaultFont();
    if (SUCCEEDED(hr))
    {
        _hfnt = _hfntDefault;
        _OnFontChange();
        if (IsFlagSet(((CREATESTRUCT *)lParam)->style, BES_TOOLBAR))
        {
            hr = _CreateToolbar();
        }

        if (SUCCEEDED(hr) && SUCCEEDED(BufferedPaintInit()))
        {
            _fBufferedPaint = TRUE;
        }
    }
    return SUCCEEDED(hr);
}

LRESULT CBinaryEdit::_OnNcDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    _CloseThemeData();
    if (_hfntDefault)
    {
        DeleteObject(_hfntDefault);
        _hfntDefault = NULL;
    }
    if (_fBufferedPaint)
    {
        BufferedPaintUnInit();
    }
    return 0;
}

LRESULT CBinaryEdit::_OnDestroy(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (IsFlagSet(GetWindowLong(GWL_STYLE), BES_TOOLBAR))
    {
        _DestroyToolbar();
    }
    return 0;
}

LRESULT CBinaryEdit::_OnSetFocus(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    CreateSolidCaret(IsFlagSet(_dwState, BES_OVERWRITE) ? _tm.tmAveCharWidth : GetSystemMetrics(SM_CXBORDER), _cyRow);
    _SetCaretPos(0, SCP_SHOW | SCP_NOMOVE);
    return 0;
}

LRESULT CBinaryEdit::_OnKillFocus(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    DestroyCaret();
    return 0;
}

void CBinaryEdit::_DestroyToolbar()
{
    HIMAGELIST himl = (HIMAGELIST)_wndToolbar.SendMessage(TB_SETIMAGELIST, 0, NULL);
    if (himl)
    {
        ImageList_Destroy(himl);
    }
    _wndToolbar.DestroyWindow();
}

LRESULT CBinaryEdit::_OnStyleChanging(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (wParam == GWL_STYLE)
    {
        STYLESTRUCT *pss = (STYLESTRUCT *)lParam;
        DWORD dwChanged = pss->styleOld ^ pss->styleNew;
        if (IsFlagSet(dwChanged, BES_TOOLBAR))
        {
            if (IsFlagSet(pss->styleNew, BES_TOOLBAR))
            {
                if (FAILED(_CreateToolbar()))
                {
                    ClearFlag(pss->styleNew, BES_TOOLBAR);
                }
            }
            else
            {
                _DestroyToolbar();
            }
        }
    }
    return 0;
}

LRESULT CBinaryEdit::_OnSetFont(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return 0;
}

LRESULT CBinaryEdit::_OnSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (_wndToolbar.IsWindow())
    {
        CRect rcToolbar;
        _CalcPartRect(BEP_TOOLBAR, &rcToolbar);
        _wndToolbar.SetWindowPos(NULL, rcToolbar.left, rcToolbar.top, rcToolbar.Width(), rcToolbar.Height() - 1, SWP_NOZORDER | SWP_NOACTIVATE);
    }
    _RecalcLayout();
    InvalidateRect(NULL, FALSE);
    return 0;
}

LRESULT CBinaryEdit::_OnEraseBkgnd(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return TRUE;
}

#define DCX_USESTYLE         0x00010000L
#define DCX_NODELETERGN      0x00040000L

LRESULT CBinaryEdit::_OnNcPaint(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (_fThemed && GetWindowLong(GWL_EXSTYLE) & WS_EX_CLIENTEDGE)
    {
        HRGN hrgnUpdate = (HRGN)(wParam != 1 ? wParam : NULL);
        DWORD dwFlags = DCX_USESTYLE | DCX_WINDOW | DCX_LOCKWINDOWUPDATE;
        if (hrgnUpdate)
        {
            dwFlags |= DCX_INTERSECTRGN | DCX_NODELETERGN;
        }

        HDC hdc = GetDCEx(hrgnUpdate, dwFlags);
        if (hdc)
        {
            // Determine the right part to draw
            DWORD dwStyle = GetWindowLong(GWL_STYLE);
            int iPartId;
            if (dwStyle & WS_HSCROLL)
            {
                if (dwStyle & WS_VSCROLL)
                {
                    // both horizontal and vertical scrollbars
                    iPartId = EP_EDITBORDER_HVSCROLL;
                }
                else
                {
                    // just a horizontal scrollbar
                    iPartId = EP_EDITBORDER_HSCROLL;
                }
            }
            else if (dwStyle & WS_VSCROLL)
            {
                // just a vertical scrollbar
                iPartId = EP_EDITBORDER_VSCROLL;
            }
            else
            {
                // no scrollbars
                iPartId = EP_EDITBORDER_NOSCROLL;
            }

            // Get the border width
            MARGINS margins;
            UINT cxBorder;
            UINT cyBorder;
            if (SUCCEEDED(GetThemeMargins(_hthmListview, NULL, 0, 0, TMT_SIZINGMARGINS, NULL, &margins)))
            {
                cxBorder = margins.cxLeftWidth;
                cyBorder = margins.cyTopHeight;
            }
            else
            {
                cxBorder = GetSystemMetrics(SM_CXBORDER);
                cyBorder = GetSystemMetrics(SM_CYBORDER);
            }

            // Set up the drawing and exclude rects
            CRect rc;
            GetWindowRect(&rc);
            rc.OffsetRect(-rc.left, -rc.top);

            CRect rcExclude = rc;
            rcExclude.InflateRect(-GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));

            ExcludeClipRect(hdc, rcExclude.left, rcExclude.top, rcExclude.right, rcExclude.bottom);

            // Draw
            if (IsThemeBackgroundPartiallyTransparent(_hthmListview, 0, 0))
            {
                if (FAILED(DrawThemeParentBackground(m_hWnd, hdc, &rc)))
                {
                    FillRect(hdc, &rc, GetSysColorBrush(COLOR_BTNFACE));
                }
            }
            DrawThemeBackground(_hthmListview, hdc, 0, 0, &rc, 0);

            // Let DefWindowProc handle scrollbars
            GetWindowRect(&rc);
            rc.InflateRect(-GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));
            HRGN hrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
            if (hrgnUpdate != NULL)
            {
                CombineRgn(hrgn, hrgnUpdate, hrgn, RGN_AND);
            }
            CWindowImpl<CBinaryEdit>::DefWindowProc(WM_NCPAINT, (WPARAM)hrgn, 0);
            DeleteObject(hrgn);

            ReleaseDC(hdc);

            fHandled = TRUE;
        }
    }
    return 0;
}


HBRUSH CBinaryEdit::_CreateModifiedSysColorBrush(int nIndex, int nNumerator, int nDenominator)
{
    COLORREF cr = GetSysColor(nIndex);
    BYTE r = GetRValue(cr);
    BYTE g = GetGValue(cr);
    BYTE b = GetBValue(cr);
    BYTE rNew;
    BYTE gNew;
    BYTE bNew;

    if (r > g && r > b)
    {
        rNew = 255;
        gNew = g + MulDiv((255 - g), nNumerator, nDenominator);
        bNew = b + MulDiv((255 - b), nNumerator, nDenominator);
    }
    else if (g > r && g > b)
    {
        gNew = 255;
        rNew = r + MulDiv((255 - r), nNumerator, nDenominator);
        bNew = b + MulDiv((255 - b), nNumerator, nDenominator);
    }
    else
    {
        rNew = r + MulDiv((255 - r), nNumerator, nDenominator);
        gNew = g + MulDiv((255 - g), nNumerator, nDenominator);
        bNew = 255;
    }
    return CreateSolidBrush(RGB(rNew, gNew, bNew));
}

HRESULT CBinaryEdit::_CreateDrawContext(__out DRAWCONTEXT *pdc)
{
    HRESULT hr = E_FAIL;
    pdc->hbrRowHdrEven = _CreateModifiedSysColorBrush(COLOR_HIGHLIGHT, 7, 8);
    if (pdc->hbrRowHdrEven)
    {
        pdc->hbrRowHdrOdd = _CreateModifiedSysColorBrush(COLOR_HIGHLIGHT, 5, 6);
        if (pdc->hbrRowHdrEven)
        {
            pdc->hbrRowOdd = _CreateModifiedSysColorBrush(COLOR_HIGHLIGHT, 9, 10);
            if (pdc->hbrRowOdd)
            {
                pdc->hbrDivider = GetSysColorBrush(COLOR_3DFACE);
                if (pdc->hbrDivider)
                {
                    hr = S_OK;
                }
            }
        }
    }
    return hr;
}

void CBinaryEdit::_Draw(HDC hdc, const CRect &rcBounds, const CRect &rcInvalid)
{
    CRect rcInt;

    int nOldMode = SetBkMode(hdc, TRANSPARENT);

    // Fill the background
    HGDIOBJ hbrOld = SelectObject(hdc, GetSysColorBrush(COLOR_WINDOW));
    PatBlt(hdc, rcInvalid.left, rcInvalid.top, rcInvalid.Width(), rcInvalid.Height(), PATCOPY);
    SelectObject(hdc, hbrOld);

    CRect rcHeader;
    _CalcPartRect(BEP_HEADER, &rcHeader);
    if (rcInt.IntersectRect(&rcHeader, &rcInvalid))
    {
        _DrawHeader(hdc, rcHeader);
    }
    // Draw the toolbar background
    CRect rcToolbar;
    _CalcPartRect(BEP_TOOLBAR, &rcToolbar);
    if (rcInt.IntersectRect(&rcToolbar, &rcInvalid))
    {
        if (_fThemed)
        {
            DrawThemeBackground(_hthmHeader, hdc, HP_HEADERITEM, HIS_SORTEDNORMAL, &rcToolbar, NULL);
        }
        else
        {
            HBRUSH hbr = GetSysColorBrush(COLOR_3DFACE);
            HGDIOBJ hbrOld = SelectObject(hdc, hbr);
            PatBlt(hdc, rcToolbar.left, rcToolbar.top, rcToolbar.Width(), rcToolbar.Height(), PATCOPY);
            SelectObject(hdc, hbrOld);
        }
    }

    CRect rcBody;
    _CalcPartRect(BEP_BODY, &rcBody);
    if (rcInt.IntersectRect(rcBody, rcInvalid))
    {
        DRAWCONTEXT dc;
        if (SUCCEEDED(_CreateDrawContext(&dc)))
        {
            CRect rcRow;
            rcRow.left = rcBody.left;
            rcRow.top = rcBody.top;
            rcRow.right = rcBody.right;
            rcRow.bottom = rcRow.top + _cyRow;
            size_t iRowMax = min(_iRowOrigin + _RowsPerPage(), _cRows);
            for (size_t iRow = _iRowOrigin; iRow < _cRows && iRow < iRowMax; iRow++)
            {
                if (rcInt.IntersectRect(rcRow, rcInvalid))
                {
                    _DrawRow(hdc, dc, iRow, rcRow);
                }
                rcRow.OffsetRect(0, _cyRow);
            }
        }
    }

    SetBkMode(hdc, nOldMode);
}

static const WCHAR *c_pszDigits[] =
{
    L"0", L"1", L"2", L"3", L"4", L"5", L"6", L"7",
    L"8", L"9", L"a", L"b", L"c", L"d", L"e", L"f",
};

void CBinaryEdit::_DrawHeader(HDC hdc, const CRect &rc)
{
    // Draw the toolbar background
    CRect rcBkgnd;
    rcBkgnd.left = rc.left;
    rcBkgnd.top = rc.top;
    rcBkgnd.right = rcBkgnd.left + _cxRowHeader;
    rcBkgnd.bottom = rc.bottom;
    if (_fThemed)
    {
        DrawThemeBackground(_hthmHeader, hdc, HP_HEADERITEM, HIS_SORTEDNORMAL, &rcBkgnd, NULL);
    }
    else
    {
        HBRUSH hbr = GetSysColorBrush(COLOR_3DFACE);
        HGDIOBJ hbrOld = SelectObject(hdc, hbr);
        PatBlt(hdc, rcBkgnd.left, rcBkgnd.top, rcBkgnd.Width(), rcBkgnd.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }

    // Draw the hex header background
    rcBkgnd.left = rcBkgnd.right;
    rcBkgnd.right = rcBkgnd.left + (2 * c_cxGridPadding) + (_cHexCols * _cxHexCell);
    if (_fThemed)
    {
        DrawThemeBackground(_hthmHeader, hdc, HP_HEADERITEM, HIS_SORTEDNORMAL, &rcBkgnd, NULL);
    }
    else
    {
        HBRUSH hbr = GetSysColorBrush(COLOR_3DFACE);
        HGDIOBJ hbrOld = SelectObject(hdc, hbr);
        PatBlt(hdc, rcBkgnd.left, rcBkgnd.top, rcBkgnd.Width(), rcBkgnd.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }

    // Draw the char header background
    rcBkgnd.left = rcBkgnd.right;
    rcBkgnd.right = rcBkgnd.left + ((_cHexCols * _cbHexCell) * _cxCharCell) + (2 * c_cxGridPadding);
    if (_fThemed)
    {
        DrawThemeBackground(_hthmHeader, hdc, HP_HEADERITEM, HIS_SORTEDNORMAL, &rcBkgnd, NULL);
    }
    else
    {
        HBRUSH hbr = GetSysColorBrush(COLOR_3DFACE);
        HGDIOBJ hbrOld = SelectObject(hdc, hbr);
        PatBlt(hdc, rcBkgnd.left, rcBkgnd.top, rcBkgnd.Width(), rcBkgnd.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }

    HGDIOBJ hfntOld = SelectObject(hdc, _hfnt);

    // Now the hex labels
    SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));

    CRect rcLabel;
    rcLabel.left = rc.left + _cxRowHeader + c_cxGridPadding + c_cxHexCellPadding;
    rcLabel.top = rc.top;
    rcLabel.right = rcLabel.left + _tm.tmAveCharWidth * 2;
    rcLabel.bottom = rcLabel.top + _cyHeader;
    for (UINT i = 0; i < _cHexCols; i++)
    {
        for (UINT j = 0; j < _cbHexCell; j++)
        {
            UINT iDigit = ((i*_cbHexCell + j) & 0x000f);
            DrawText(hdc, c_pszDigits[iDigit], -1, &rcLabel, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOCLIP);
            rcLabel.OffsetRect(_tm.tmAveCharWidth * 2, 0);
        }

        rcLabel.OffsetRect(2*c_cxHexCellPadding, 0);
    }

    // Now the char labels
    rcLabel.left += (2 * c_cxGridPadding) - c_cxHexCellPadding;
    for (UINT i = 0; i < _cHexCols * _cbHexCell; i++)
    {
        rcLabel.right = rcLabel.left + _cxCharCell;
        UINT iDigit = ((BYTE)i) & 0x000f;
        DrawText(hdc, c_pszDigits[iDigit], -1, &rcLabel, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOCLIP);
        rcLabel.left = rcLabel.right;
    }

    SelectObject(hdc, hfntOld);
}

HRESULT CBinaryEdit::_BinaryToText(BYTE b, __out PWSTR pszText, DWORD cch)
{
    const WCHAR c_wcDigits[] =
    {
        L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7',
        L'8', L'9', L'a', L'b', L'c', L'd', L'e', L'f',
    };
    HRESULT hr = E_FAIL;
    if (cch >= 3)
    {
        hr = S_OK;
        PWSTR pszTemp = pszText;
        *pszTemp++ = c_wcDigits[(b >> 4) & 0x000f];
        *pszTemp++ = c_wcDigits[b & 0x000f];
        *pszTemp = '\0';
    }
    return hr;
}

BOOL CBinaryEdit::_IsByteSelected(size_t iIndex)
{
    BOOL fRet = FALSE;
    if (_iSelStart != _iSelEnd)
    {
        if ((_iSelStart <= _iSelEnd && (iIndex >= _iSelStart && iIndex <= _iSelEnd - 1)) ||
            (_iSelEnd < _iSelStart && (iIndex >= _iSelEnd && iIndex < _iSelStart)))
        {
            fRet = TRUE;
        }
    }
    return fRet;
}

void CBinaryEdit::_DrawRow(HDC hdc, const DRAWCONTEXT &dc, size_t iRow, const CRect &rc)
{
    // Draw the row header
    CRect rcHeader = rc;
    rcHeader.right = rcHeader.left + _cxRowHeader;
    if (_fAlternateRowColor && iRow % 2)
    {
        HGDIOBJ hbrOld = SelectObject(hdc, dc.hbrRowHdrOdd);
        PatBlt(hdc, rcHeader.left, rcHeader.top, rcHeader.Width(), rcHeader.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }
    else
    {
        HGDIOBJ hbrOld = SelectObject(hdc, dc.hbrRowHdrEven);
        PatBlt(hdc, rcHeader.left, rcHeader.top, rcHeader.Width(), rcHeader.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }

    HGDIOBJ hfntOld = SelectObject(hdc, _hfnt);
    CStackString<64> strHeader;
    if (SUCCEEDED(StringCchPrintf(strHeader, strHeader.Size(), L"0x%0.8x:", iRow * (_cHexCols * _cbHexCell))))
    {
        SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
        DrawText(hdc, strHeader, -1, &rcHeader, DT_SINGLELINE | DT_RIGHT | DT_NOCLIP | DT_VCENTER);
    }

    // Fill the background
    if (_fAlternateRowColor && iRow % 2)
    {
        CRect rcRow = rc;
        rcRow.left += _cxRowHeader;

        HGDIOBJ hbrOld = SelectObject(hdc, dc.hbrRowOdd);
        PatBlt(hdc, rcRow.left, rcRow.top, rcRow.Width(), rcRow.Height(), PATCOPY);
        SelectObject(hdc, hbrOld);
    }

    UINT cxHexGrid = (_cHexCols * _cxHexCell) + (2 * c_cxGridPadding);

    // Draw the hex and char cells in parallel
    CRect rcHexCell;
    rcHexCell.left = rc.left + _cxRowHeader + c_cxGridPadding;
    rcHexCell.top = rc.top;
    rcHexCell.right = rcHexCell.left + _cxHexCell;
    rcHexCell.bottom = rcHexCell.top + _cyRow;

    CRect rcCharCell;
    rcCharCell.left = rc.left + _cxRowHeader + cxHexGrid + c_cxGridPadding;
    rcCharCell.top = rc.top;
    rcCharCell.right = rcCharCell.left + _cxCharCell;
    rcCharCell.bottom = rcCharCell.top + _cyRow;
    size_t iIndex = iRow * _cbRow;
    for (UINT i = 0; i < _cHexCols && iIndex < _rgData.GetCount(); i++)
    {
        // Build a string for the current hex cell type
        CRect rcText = rcHexCell;
        rcText.InflateRect(-(int)c_cxHexCellPadding, 0);
        rcText.right = rcText.left + (2 * _tm.tmAveCharWidth);
        for (UINT j = 0; j < _cbHexCell && iIndex < _rgData.GetCount(); j++)
        {
            WORD wData = _rgData.GetAt(iIndex);
            BOOL fSelected = _IsByteSelected(iIndex);
            if (LOBYTE(wData))
            {
                SetTextColor(hdc, RGB(255,0,0));
            }
            else
            {
                SetTextColor(hdc, GetSysColor(fSelected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
            }

            // Draw the hex cell
            if (fSelected)
            {
                CRect rcSel = rcText;
                if (j == 0 && i != 0)
                {
                    rcSel.left -= c_cxHexCellPadding;
                }
                if (j == _cbHexCell - 1 && i < _cHexCols - 1)
                {
                    rcSel.right += c_cxHexCellPadding;
                }
                HGDIOBJ hbrOld = SelectObject(hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
                PatBlt(hdc, rcSel.left, rcSel.top, rcSel.Width(), rcSel.Height(), PATCOPY);
                SelectObject(hdc, hbrOld);
            }

            CByteString str;
            if (SUCCEEDED(_BinaryToText(HIBYTE(wData), str, str.Size())))
            {
                DrawText(hdc, str, -1, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_NOCLIP);
            }
            rcText.OffsetRect(2 * _tm.tmAveCharWidth, 0);

            // Draw the char cell
            if (fSelected)
            {
                CRect rcSel = rcCharCell;
                if (i == 0 && j == 0)
                {
                    rcSel.left += c_cxCharCellPadding;
                }
                if (i == _cHexCols - 1 && j == _cbHexCell - 1)
                {
                    rcSel.right -= c_cxCharCellPadding;
                }
                HGDIOBJ hbrOld = SelectObject(hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
                PatBlt(hdc, rcSel.left, rcSel.top, rcSel.Width(), rcSel.Height(), PATCOPY);
                SelectObject(hdc, hbrOld);
            }

            char sz[2];
            BYTE b = HIBYTE(wData);
            sz[0] = (b & 0x7f) >= ' ' ? b : '.';
            sz[1] = 0;
            DrawTextA(hdc, sz, -1, &rcCharCell, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOCLIP);
            rcCharCell.OffsetRect(_cxCharCell, 0);

            iIndex++;
        }
        rcHexCell.OffsetRect(_cxHexCell, 0);
    }

    // Draw the divider lines
    if (_hcdt != HCDT_NONE)
    {
        // Determine the byte boundary for drawing dividers
        UINT cbDivider;
        switch (_hcdt)
        {
        case HCDT_BYTE:
            cbDivider = sizeof(BYTE);
            break;

        case HCDT_WORD:
            cbDivider = sizeof(WORD);
            break;

        case HCDT_DWORD:
            cbDivider = sizeof(DWORD);
            break;

        case HCDT_QWORD:
            cbDivider = sizeof(DWORDLONG);
            break;

        case HCDT_AUTO:
            cbDivider = _cbHexCell * _cMinHexCols;
            break;
        }

        div_t divRes = div((int)_cbRow, (int)cbDivider);
        UINT cDividers = divRes.quot - 1 + (divRes.rem ? 1 : 0);
        UINT cxAdjust = 0;
        if (cbDivider > _cbHexCell)
        {
            cxAdjust += ((cbDivider / _cbHexCell) - 1) * (2 * c_cxHexCellPadding);
        }

        CRect rcDivider;
        rcDivider.left = rc.left + _cxRowHeader + c_cxGridPadding + c_cxHexCellPadding - (_sizeDivider.cx / 2);
        rcDivider.top = rc.top - 1;
        rcDivider.right = rcDivider.left + _sizeDivider.cx;
        rcDivider.bottom = rc.bottom;
        UINT cbTotal = 0;
        for (UINT iDivider = 0; iDivider < cDividers; iDivider++)
        {
            rcDivider.OffsetRect(((cbDivider * 2) * _tm.tmAveCharWidth) + cxAdjust, 0);
            cbTotal += cbDivider;
            BOOL fAtColumnSeparator = !(cbTotal % _cbHexCell);
            if (fAtColumnSeparator)
            {
                rcDivider.OffsetRect(c_cxHexCellPadding, 0);
            }
            if (_fThemed)
            {
                DrawThemeBackground(_hthmListview, hdc, LVP_COLUMNDETAIL, 0, &rcDivider, NULL);
            }
            else
            {
                HBRUSH hbr = GetSysColorBrush(COLOR_3DFACE);
                HGDIOBJ hbrOld = SelectObject(hdc, hbr);
                PatBlt(hdc, rcDivider.top, rcDivider.left, rcDivider.Width(), rcDivider.Height(), PATCOPY);
                SelectObject(hdc, hbrOld);
            }
            if (fAtColumnSeparator)
            {
                rcDivider.OffsetRect(c_cxHexCellPadding, 0);
            }
        }
    }

    // Now the grid divider
    CRect rcGridDivider;
    rcGridDivider.left = (rc.left + _cxRowHeader + cxHexGrid) - (_sizeDivider.cx / 2);
    rcGridDivider.top = rc.top - 1;
    rcGridDivider.right = rcGridDivider.left + _sizeDivider.cx;
    rcGridDivider.bottom = rc.bottom;

    HGDIOBJ hbrOld = SelectObject(hdc, dc.hbrDivider);
    PatBlt(hdc, rcGridDivider.left, rcGridDivider.top, rcGridDivider.Width(), rcGridDivider.Height(), PATCOPY);
    SelectObject(hdc, hbrOld);

    SelectObject(hdc, hfntOld);
}

LRESULT CBinaryEdit::_OnPrintClient(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    CRect rcClient;
    GetClientRect(&rcClient);
    _Draw((HDC)wParam, rcClient, rcClient);
    return 0;
}

LRESULT CBinaryEdit::_OnPaint(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(&ps);
    if (hdc)
    {
        CRect rcInvalid = ps.rcPaint;
        CRect rcClient;
        GetClientRect(&rcClient);

        HDC hdcDraw = hdc;
        HPAINTBUFFER hpb = NULL;
        if (_fBufferedPaint)
        {
            hpb = BeginBufferedPaint(hdc, &rcInvalid, BPBF_TOPDOWNDIB, NULL, &hdcDraw);
        }

        _Draw(hdcDraw, rcClient, rcInvalid);

        if (hdcDraw != ps.hdc)
        {
            EndBufferedPaint(hpb, TRUE);
        }
        EndPaint(&ps);
    }
    return 0;
}

void CBinaryEdit::_ScrollContents(int cLines)
{
    CRect rcScroll;
    _CalcPartRect(BEP_BODY, &rcScroll);
    ScrollWindowEx(0, -cLines * _cyRow, &rcScroll, &rcScroll, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN);
    _iRowOrigin += cLines;
    _UpdateScrollInfo();
    _SetCaretPos(0, SCP_NOMOVE | (_IsByteVisible(_iCaretPos) ? SCP_SHOW : SCP_HIDE));
}

LRESULT CBinaryEdit::_OnVScroll(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    switch (LOWORD(wParam))
    {
    case SB_LINEDOWN:
        if (_iRowOrigin < _iMaxRowOrigin)
        {
            _ScrollContents(1);
        }
        break;

    case SB_LINEUP:
        if (_iRowOrigin)
        {
            _ScrollContents(-1);
        }
        break;

    case SB_PAGEDOWN:
        if (_iRowOrigin < _iMaxRowOrigin)
        {
            size_t iNewOrigin = _iRowOrigin + _RowsPerPage();
            _iRowOrigin = min(_iMaxRowOrigin, iNewOrigin);
            InvalidateRect(NULL, FALSE);
        }
        break;

    case SB_PAGEUP:
        if (_iRowOrigin)
        {
            int iNewOrigin = (int)_iRowOrigin - _RowsPerPage();
            _iRowOrigin = max(0, iNewOrigin);
            InvalidateRect(NULL, FALSE);
        }
        break;

    case SB_BOTTOM:
        if (_iRowOrigin != _iMaxRowOrigin)
        {
            _iRowOrigin = _iMaxRowOrigin;
            InvalidateRect(NULL, FALSE);
        }
        break;

    case SB_TOP:
        if (_iRowOrigin)
        {
            _iRowOrigin = 0;
            InvalidateRect(NULL, FALSE);
        }
        break;

    case SB_THUMBTRACK:
        {
            UINT iPos = HIWORD(wParam);
            int iRowDelta = iPos - _iRowOrigin;
            if (iRowDelta < (int)_IntegralRowsPerPage())
            {
                _ScrollContents(iRowDelta);
            }
            else
            {
                _iRowOrigin += iRowDelta;
                InvalidateRect(NULL, FALSE);
            }
            UpdateWindow();
        }
        break;

    default:
        break;
    }
    _UpdateScrollInfo();
    return 0;
}

LRESULT CBinaryEdit::_OnMouseWheel(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    int iLines = -(GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA);
    if (iLines > 0 && _iRowOrigin < _iMaxRowOrigin)
    {
        iLines = min(iLines, (int)(_iMaxRowOrigin - _iRowOrigin));
        _ScrollContents(iLines);
    }
    if (iLines < 0 && _iRowOrigin)
    {
        iLines = min(iLines, (int)_iRowOrigin);
        _ScrollContents(iLines);
    }
    return 0;
}

void CBinaryEdit::_CalcPartRect(BINARYEDITPART bep, __out CRect *prc)
{
    CRect rcClient;
    GetClientRect(&rcClient);
    switch (bep)
    {
    case BEP_TOOLBAR:
        prc->left = rcClient.left;
        prc->top = rcClient.top;
        prc->right = prc->left + _cxRowHeader;
        prc->bottom = prc->top + _cyHeader;
        break;

    case BEP_HEADER:
        prc->left = rcClient.left;
        prc->top = rcClient.top;
        prc->right = prc->left + _cxRowHeader + (4 * c_cxGridPadding) + (_cHexCols * _cxHexCell) + (_cbRow * _cxCharCell);
        prc->bottom = prc->top + _cyHeader;
        break;

    case BEP_ROWHEADER:
        prc->left = rcClient.left;
        prc->top = rcClient.top + _cyHeader;
        prc->right = prc->left + _cxRowHeader;
        prc->bottom = rcClient.bottom;
        break;

    case BEP_BODY:
        prc->left = rcClient.left;
        prc->top = rcClient.top + _cyHeader;
        prc->right = prc->left + _cxRowHeader + (4 * c_cxGridPadding) + (_cHexCols * _cxHexCell) + (_cbRow * _cxCharCell);
        prc->bottom = rcClient.bottom;
        break;

    case BEP_HEXGRID:
        prc->left = rcClient.left + _cxRowHeader + c_cxGridPadding;;
        prc->top = rcClient.top + _cyHeader;
        prc->right = prc->left + (_cHexCols * _cxHexCell);
        prc->bottom = rcClient.bottom;
        break;

    case BEP_VIRTUALHEXGRID:
        prc->left = rcClient.left + _cxRowHeader + c_cxGridPadding;;
        prc->top = rcClient.top + _cyHeader - (LONG)(_iRowOrigin * _cyRow);
        prc->right = prc->left + (_cHexCols * _cxHexCell);
        prc->bottom = prc->top + (_cRows * _cyRow);
        break;

    case BEP_CHARGRID:
        prc->left = rcClient.left + _cxRowHeader + (_cHexCols * _cxHexCell) + (3 * c_cxGridPadding);
        prc->top = rcClient.top + _cyHeader;
        prc->right = prc->left + (_cbRow * _cxCharCell);
        prc->bottom = rcClient.bottom;
        break;

    case BEP_VIRTUALCHARGRID:
        prc->left = rcClient.left + _cxRowHeader + (_cHexCols * _cxHexCell) + (3 * c_cxGridPadding);
        prc->top = rcClient.top + _cyHeader - (LONG)(_iRowOrigin * _cyRow);
        prc->right = prc->left + (_cbRow * _cxCharCell);
        prc->bottom = prc->top + (_cRows * _cyRow);
        break;

    }
}

void CBinaryEdit::_EnsureVisible(size_t iByte)
{
    size_t iRow;
    size_t iCol;
    _CalcByteCoordinates(iByte, TRUE, &iRow, &iCol);

    int iLines = 0;
    if (iRow < _iRowOrigin)
    {
        iLines = (int)(iRow - _iRowOrigin);
    }
    else if (iRow >= _iRowOrigin + _IntegralRowsPerPage())
    {
        iLines = (int)(iRow - (_iRowOrigin + _IntegralRowsPerPage()) + 1);
    }
    if (iLines != 0)
    {
        _ScrollContents(iLines);
    }
}

void CBinaryEdit::_CalcByteCoordinates(size_t iByte, BOOL fCharGrid, __out size_t *piRow, __out size_t *piCol)
{
    sdiv_t divRes = {};
    if (iByte)
    {
        divRes = sdiv(iByte, _cbRow);
    }
    *piRow = divRes.quot;
    if (fCharGrid)
    {
        *piCol = divRes.rem;
    }
    else
    {
        *piCol = 0;
        if (divRes.rem)
        {
            *piCol = divRes.rem / _cbHexCell;
        }
    }
}

void CBinaryEdit::_CalcCaretPos(size_t iByte, __out POINT *pt)
{
    size_t iRow;
    size_t iCol;
    _CalcByteCoordinates(iByte, IsFlagSet(_dwState, BES_CHARGRIDFOCUS), &iRow, &iCol);

    pt->y = (LONG)((iRow - _iRowOrigin) * _cyRow) + _cyHeader;
    if (IsFlagSet(_dwState, BES_CHARGRIDFOCUS))
    {
        pt->x = _cxRowHeader + (3 * c_cxGridPadding) + (_cHexCols * _cxHexCell) + (iCol * _cxCharCell) + c_cxCharCellPadding;
    }
    else
    {
        size_t iByteIndex = (iByte - (iRow * _cbRow)) - (iCol * _cbHexCell);
        pt->x = (LONG)(_cxRowHeader + c_cxGridPadding + (iCol * _cxHexCell) + c_cxHexCellPadding + (iByteIndex * (2 * _tm.tmAveCharWidth)));
        if (IsFlagSet(_dwState, BES_FOCUSONLOWNIBBLE))
        {
            pt->x += _tm.tmAveCharWidth;
        }
    }
}
    
void CBinaryEdit::_SetCaretPos(size_t iByte, DWORD dwFlags)
{
    // The order of flag evaluation is important
    if (IsFlagSet(dwFlags, SCP_ADJUSTSELECTION))
    {
        if (IsFlagSet(_dwState, BES_CAPTURE))
        {
            if (iByte >= _iSelStart)
            {
                iByte = min(iByte + 1, _rgData.GetCount());
                _iSelEnd = iByte;
            }
            else
            {
                _iSelEnd = iByte;
            }
        }
        else
        {
            _iSelEnd = iByte;
        }
    }
    else if (IsFlagClear(dwFlags, SCP_NOMOVE))
    {
        _iSelStart = iByte;
        _iSelEnd = iByte;
    }

    if (IsFlagClear(dwFlags, SCP_NOMOVE))
    {
        _iCaretPos = iByte;
        if (IsFlagSet(dwFlags, SCP_LOWNIBBLE))
        {
            SetFlag(_dwState, BES_FOCUSONLOWNIBBLE);
        }
        else
        {
            ClearFlag(_dwState, BES_FOCUSONLOWNIBBLE);
        }

        if (_IsByteVisible(iByte))
        {
            CPoint ptCaret;
            _CalcCaretPos(iByte, &ptCaret);
            SetCaretPos(ptCaret.x, ptCaret.y);
        }

        if (IsFlagSet(dwFlags, SCP_ENSUREVISIBLE))
        {
            // This can cause reentrancy
            _EnsureVisible(iByte);
            ClearFlag(dwFlags, SCP_ENSUREVISIBLE);
        }
    }
    else
    {
        iByte = _iCaretPos;
    }

    if (IsFlagSet(dwFlags, SCP_ENSUREVISIBLE))
    {
        _EnsureVisible(iByte);
    }

    if (IsFlagSet(dwFlags, SCP_SHOW))
    {
        if (_IsByteVisible(iByte))
        {
            CPoint ptCaret;
            _CalcCaretPos(iByte, &ptCaret);
            SetCaretPos(ptCaret.x, ptCaret.y);
            ShowCaret();
            SetFlag(_dwState, BES_CARETVISIBLE);
        }
    }
    else if (IsFlagSet(dwFlags, SCP_HIDE) && IsFlagSet(_dwState, BES_CARETVISIBLE))
    {
        HideCaret();
        ClearFlag(_dwState, BES_CARETVISIBLE);
    }

    // TODO: optimize this
    InvalidateRect(NULL, FALSE);
}

void CBinaryEdit::_HitTestHexCell(const CPoint &pt, const CRect &rcVHexGrid, HITTESTTYPE htt, __inout HITTEST *pht)
{
    size_t iRow;
    if (pt.y < rcVHexGrid.top)
    {
        iRow = 0;
    }
    else if (pt.y >= rcVHexGrid.bottom)
    {
        iRow = _cRows - 1;
    }
    else
    {
        iRow = (pt.y - rcVHexGrid.top) / _cyRow;
    }
    size_t iCol = (pt.x - rcVHexGrid.left) / _cxHexCell;
    pht->iByte = (iRow * _cbRow) + (iCol * _cbHexCell);

    size_t cxHalfChar = (_tm.tmAveCharWidth / 2) + (_tm.tmAveCharWidth % 2);
    // Determine which byte we're over
    CRect rcByte;
    rcByte.left = _cxRowHeader + c_cxGridPadding + (LONG)(iCol * _cxHexCell);
    rcByte.top = pt.y - 1;
    rcByte.right = rcByte.left + c_cxHexCellPadding + (2 * _tm.tmAveCharWidth) + (_hct == HCT_BYTE ? c_cxHexCellPadding : 0);
    rcByte.bottom = pt.y + 1;
    for (size_t iCellByte = 0; iCellByte < _cbHexCell && pt.x >= rcByte.left; iCellByte++)
    {
        if (rcByte.PtInRect(pt))
        {
            pht->iByte += iCellByte;
            if (iCellByte == 0)
            {
                if (htt == HTT_FORINSERTION)
                {
                    if (pt.x > (int)(rcByte.right - (cxHalfChar + (_hct == HCT_BYTE ? c_cxHexCellPadding : 0))))
                    {
                        pht->iByte++;
                    }
                    else if (pt.x >= (int)(rcByte.left + c_cxHexCellPadding + cxHalfChar))
                    {
                        pht->fLowNibble = TRUE;
                    }
                }
                else if (pt.x > (int)(rcByte.left + c_cxHexCellPadding + _tm.tmAveCharWidth))
                {
                    pht->fLowNibble = TRUE;
                }
            }
            else if (iCellByte == _cbHexCell - 1)
            {
                if (htt == HTT_FORINSERTION)
                {
                    if (pt.x > (int)(rcByte.right - (cxHalfChar + c_cxHexCellPadding)))
                    {
                        pht->iByte++;
                    }
                    else if (pt.x >= (int)(rcByte.left + cxHalfChar))
                    {
                        pht->fLowNibble = TRUE;
                    }
                }
                else if (pt.x > rcByte.left + _tm.tmAveCharWidth)
                {
                    pht->fLowNibble = TRUE;
                }
            }
            else
            {
                if (htt == HTT_FORINSERTION)
                {
                    if (pt.x > rcByte.right - (int)cxHalfChar)
                    {
                        pht->iByte++;
                    }
                    else if (pt.x >= (int)(rcByte.left + cxHalfChar))
                    {
                        pht->fLowNibble = TRUE;
                    }
                }
                else
                {
                    if (pt.x > rcByte.CenterPoint().x)
                    {
                        pht->fLowNibble = TRUE;
                    }
                }
            }
        }
        rcByte.left = rcByte.right;
        rcByte.right = rcByte.left + (_tm.tmAveCharWidth * 2) + ((iCellByte + 1 == _cbHexCell - 1) ? c_cxHexCellPadding : 0);
    }
}

void CBinaryEdit::_HitTest(const CPoint &pt, HITTESTTYPE htt, __out HITTEST *pht)
{
    pht->ht = HT_NOWHERE;

    CRect rc;
    _CalcPartRect(BEP_ROWHEADER, &rc);
    if (rc.PtInRect(pt) && htt != HTT_FORDRAG)
    {
        pht->ht = HT_ROWHEADER;
        pht->iRow = (pt.y - rc.top) / _cyRow + _iRowOrigin;
    }
    else
    {
        CRect rcVHexGrid;
        _CalcPartRect(BEP_VIRTUALHEXGRID, &rcVHexGrid);
        if (rcVHexGrid.PtInRect(pt) || (htt == HTT_FORDRAG && pt.x >= rcVHexGrid.left && pt.x < rcVHexGrid.right))
        {
            pht->ht = HT_HEXCELL;
            pht->fLowNibble = FALSE;
            _HitTestHexCell(pt, rcVHexGrid, htt, pht);
        }
        else
        {
            CRect rcVCharGrid;
            _CalcPartRect(BEP_VIRTUALCHARGRID, &rcVCharGrid);
            if (rcVCharGrid.PtInRect(pt) || (htt == HTT_FORDRAG && pt.x >= rcVCharGrid.left && pt.x < rcVCharGrid.right))
            {
                pht->ht = HT_CHARCELL;

                size_t iRow;
                if (pt.y < rcVCharGrid.top)
                {
                    iRow = 0;
                }
                else if (pt.y >= rcVCharGrid.bottom)
                {
                    iRow = _cRows - 1;
                }
                else
                {
                    iRow = (pt.y - rcVCharGrid.top) / _cyRow;
                }

                size_t iCol = ((pt.x - rcVCharGrid.left) / _cxCharCell);
                CRect rcByte;
                rcByte.left = rcVCharGrid.left + (LONG)(iCol * _cxCharCell);
                rcByte.top = pt.y - 1;
                rcByte.right = rcByte.left + _cxCharCell;
                rcByte.bottom = pt.y + 1;

                pht->iByte = (iRow * _cbRow) + iCol;
                if (htt == HTT_FORINSERTION && pt.x > rcByte.CenterPoint().x)
                {
                    pht->iByte++;
                }
                pht->fLowNibble = FALSE;
            }
            else
            {
                if (htt == HTT_FORDRAG)
                {
                    // Point is not in either of the grids, calculate the nearest byte
                    size_t iRow;
                    if (pt.y < rcVHexGrid.top)
                    {
                        iRow = 0;
                    }
                    else if (pt.y >= rcVHexGrid.bottom)
                    {
                        iRow = _cRows - 1;
                    }
                    else
                    {
                        iRow = (pt.y - rcVHexGrid.top) / _cyRow;
                    }

                    if (pt.x < rcVHexGrid.left ||
                        (pt.x >= (LONG)(rcVHexGrid.right + c_cxHexCellPadding + c_cxGridPadding) &&
                         pt.x < rcVCharGrid.left))
                    {
                        pht->ht = HT_HEXCELL;
                        pht->iByte = iRow * _cbRow;
                    }
                    else if ((pt.x >= rcVHexGrid.right && pt.x < (LONG)(rcVHexGrid.right + c_cxHexCellPadding + c_cxGridPadding)) ||
                             pt.x >= rcVCharGrid.right)
                    {
                        pht->ht = HT_CHARCELL;
                        pht->iByte = (iRow * _cbRow) + _cbRow;
                    }
                    else
                    {
                        ATLASSERT(FALSE);   // Make sure to handle all cases
                    }
                }
                else
                {
                    pht->ht = HT_NOWHERE;
                }
            }
        }
        if (pht->ht != HT_NOWHERE && pht->iByte >= _rgData.GetCount())
        {
            pht->iByte = _rgData.GetCount();
            pht->fLowNibble = FALSE;
        }
    }
}

LRESULT CBinaryEdit::_OnLButtonDown(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    CPoint pt(lParam);
    HITTEST ht;
    _HitTest(pt, IsFlagClear(_dwState, BES_OVERWRITE) ? HTT_FORINSERTION : HTT_NORMAL, &ht);
    if (ht.ht == HT_HEXCELL || ht.ht == HT_CHARCELL)
    {
        if (ht.ht == HT_HEXCELL)
        {
            ClearFlag(_dwState, BES_CHARGRIDFOCUS);
        }
        else
        {
            SetFlag(_dwState, BES_CHARGRIDFOCUS);
        }

        DWORD dwFlags = SCP_ENSUREVISIBLE;
        if (ht.fLowNibble)
        {
            SetFlag(dwFlags, SCP_LOWNIBBLE);
        }
        if (GetFocus() == m_hWnd)
        {
            SetFlag(dwFlags, SCP_SHOW);
        }
        if (wParam & MK_SHIFT)
        {
            SetFlag(dwFlags, SCP_ADJUSTSELECTION);
        }
        _SetCaretPos(ht.iByte, dwFlags);
        if (GetFocus() != m_hWnd)
        {
            SetFocus();
        }

        // Calculate a drag rect
        _rcDrag.left = pt.x - (GetSystemMetrics(SM_CXDRAG) / 2);
        _rcDrag.top = pt.y - (GetSystemMetrics(SM_CYDRAG) / 2);
        _rcDrag.right = _rcDrag.left + GetSystemMetrics(SM_CXDRAG);
        _rcDrag.bottom = _rcDrag.top + GetSystemMetrics(SM_CYDRAG);

        SetCapture();
        SetFlag(_dwState, BES_CAPTURE);
    }
    return 0;
}

LRESULT CBinaryEdit::_OnMouseMove(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    CPoint pt(lParam);
    if (IsFlagSet(_dwState, BES_CAPTURE) && !_rcDrag.PtInRect(pt))
    {
        HITTEST ht;
        _HitTest(pt, HTT_FORDRAG, &ht);
        if (ht.ht == HT_HEXCELL || ht.ht == HT_CHARCELL)
        {
            _SetCaretPos(ht.iByte, SCP_ADJUSTSELECTION | SCP_ENSUREVISIBLE);

            InvalidateRect(NULL, FALSE);
            UpdateWindow();
        }
    }
    return 0;
}

LRESULT CBinaryEdit::_OnLButtonUp(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    ReleaseCapture();
    return 0;
}

LRESULT CBinaryEdit::_OnCaptureChanged(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    ClearFlag(_dwState, BES_CAPTURE);
    return 0;
}

LRESULT CBinaryEdit::_OnSetCursor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    CPoint pt;
    GetCursorPos(&pt);
    ScreenToClient(&pt);

    CRect rc;
    _CalcPartRect(BEP_HEXGRID, &rc);
    if (rc.PtInRect(pt))
    {
        SetCursor(LoadCursor(NULL, IDC_IBEAM));
    }
    else
    {
        _CalcPartRect(BEP_CHARGRID, &rc);
        if (rc.PtInRect(pt))
        {
            SetCursor(LoadCursor(NULL, IDC_IBEAM));
        }
        else
        {
            SetCursor(LoadCursor(NULL, IDC_ARROW));
        }
    }
    return 0;
}

LRESULT CBinaryEdit::_OnGetDlgCode(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return DLGC_WANTCHARS | DLGC_WANTARROWS;
}

DWORD CBinaryEdit::_GetKeyModifiers()
{
    DWORD dwKeyModifiers = 0;
    static const struct
    {
        WORD wVirtKey;
        WORD wKeyModifier;
    }
    s_rgVKMap[] = 
    {
        { VK_SHIFT, KM_SHIFT },
        { VK_CONTROL, KM_CONTROL },
        { VK_MENU, KM_ALT },
    };

    for (UINT i = 0; i < ARRAYSIZE(s_rgVKMap); i++)
    {
        if (GetKeyState(s_rgVKMap[i].wVirtKey) < 0)
        {
            SetFlag(dwKeyModifiers, s_rgVKMap[i].wKeyModifier);
        }
    }
    return dwKeyModifiers;
}

void CBinaryEdit::_AdjustCaretPosition(UINT uiDirection, DWORD dwKeyModifiers)
{
    size_t iRow;
    size_t iCol;
    _CalcByteCoordinates(_iCaretPos, FALSE, &iRow, &iCol);

    size_t iNewCaretPos = _iCaretPos;
    BOOL fLowNibble = IsFlagSet(_dwState, BES_FOCUSONLOWNIBBLE);
    switch (uiDirection)
    {
    case VK_RIGHT:
        if (IsFlagClear(_dwState, BES_CHARGRIDFOCUS))
        {
            if (IsFlagSet(dwKeyModifiers, KM_CONTROL))
            {
                iNewCaretPos = (iRow * _cbRow) + ((iCol + 1) * _cbHexCell);
                fLowNibble = FALSE;
            }
            else
            {
                if (fLowNibble || IsFlagSet(dwKeyModifiers, KM_SHIFT))
                {
                    iNewCaretPos = _iCaretPos + 1;
                }
                fLowNibble = !fLowNibble;
            }
        }
        else
        {
            iNewCaretPos = _iCaretPos + 1;
        }
        break;

    case VK_LEFT:
        if (IsFlagClear(_dwState, BES_CHARGRIDFOCUS))
        {
            if (IsFlagSet(dwKeyModifiers, KM_CONTROL))
            {
                if (!iRow && !iCol)
                {
                    iNewCaretPos = 0;
                }
                else
                {
                    iNewCaretPos = (iRow * _cbRow) + (iCol * _cbHexCell) - _cbHexCell;
                }
                fLowNibble = FALSE;
            }
            else
            {
                if ((!fLowNibble || IsFlagSet(dwKeyModifiers, KM_SHIFT)) && _iCaretPos)
                {
                    iNewCaretPos = _iCaretPos - 1;
                }
                fLowNibble = !fLowNibble;
            }
        }
        else
        {
            if (_iCaretPos)
            {
                iNewCaretPos = _iCaretPos - 1;
            }
        }
        break;

    case VK_UP:
        if (_iCaretPos > _cbRow - 1)
        {
            iNewCaretPos = _iCaretPos - _cbRow;
        }
        break;

    case VK_DOWN:
        if (iRow < _cRows - 1)
        {
            iNewCaretPos = _iCaretPos + _cbRow;
        }
        break;

    case VK_END:
        iNewCaretPos = ((iRow + 1) * _cbRow) - 1;
        fLowNibble = TRUE;
        break;

    case VK_HOME:
        iNewCaretPos = iRow * _cbRow;
        fLowNibble = FALSE;
        break;

    case VK_NEXT:
        iNewCaretPos = _iCaretPos + (_IntegralRowsPerPage() * _cbRow);
        break;

    case VK_PRIOR:
        {
            size_t cbPage = _IntegralRowsPerPage() * _cbRow;
            if (_iCaretPos > cbPage)
            {
                iNewCaretPos = _iCaretPos - cbPage;
            }
            else
            {
                iNewCaretPos = 0;
                fLowNibble = FALSE;
            }
        }
        break;

    default:
        ATLASSERT(FALSE);   // All directions should be supported

    }

    // Adjust the cell coordinates
    if (iNewCaretPos >= _rgData.GetCount())
    {
        iNewCaretPos = _rgData.GetCount();
        fLowNibble = FALSE;
    }

    DWORD dwSCPFlags = SCP_ENSUREVISIBLE;
    if (IsFlagSet(dwKeyModifiers, KM_SHIFT))
    {
        dwSCPFlags |= SCP_ADJUSTSELECTION;
    }
    if (fLowNibble && IsFlagClear(dwKeyModifiers, KM_SHIFT))
    {
        dwSCPFlags |= SCP_LOWNIBBLE;
    }
    _SetCaretPos(iNewCaretPos, dwSCPFlags);
}

void CBinaryEdit::_DeleteText(UINT uiKey)
{
    size_t iStart;
    size_t cw = 0;

    if (_iSelStart != _iSelEnd)
    {
        if (_iSelEnd > _iSelStart)
        {
            iStart = _iSelStart;
            cw = _iSelEnd - _iSelStart;
        }
        else
        {
            iStart = _iSelEnd;
            cw = _iSelStart - _iSelEnd;
        }
    }
    else
    {
        // No selection, figure out which byte to delete
        if (uiKey == VK_BACK && _iCaretPos > 0)
        {
            iStart = _iCaretPos - 1;
            cw = 1;
        }
        else if (uiKey == VK_DELETE)
        {
            iStart = _iCaretPos;
            cw = 1;
        }
    }
    if (iStart < _rgData.GetCount() && cw)
    {
        _ModifyData(iStart, cw, NULL, 0);
        _SetCaretPos(iStart, SCP_ENSUREVISIBLE);
    }
}

HRESULT CBinaryEdit::_ModifyData(size_t iIndex, size_t cReplace, __in_opt WORD *pwNew, size_t cNew)
{
    ATLASSERT(iIndex <= _rgData.GetCount());

    if (cReplace)
    {
        _rgData.RemoveAt(iIndex, cReplace);
    }

    HRESULT hr = S_OK;
    if (cNew)
    {
        CAtlArray<WORD> rgNew;
        hr = rgNew.SetCount(cNew) ? S_OK : E_OUTOFMEMORY;
        if (SUCCEEDED(hr))
        {
            CopyMemory(rgNew.GetData(), pwNew, cNew * sizeof(WORD));
            try
            {
                _rgData.InsertArrayAt(iIndex, &rgNew);
            }
            catch (CAtlException &e)
            {
                hr = e.m_hr;
            }
        }
    }
    return hr;
}

LRESULT CBinaryEdit::_OnKeyDown(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    switch (wParam)
    {
    case VK_RIGHT:
    case VK_LEFT:
    case VK_UP:
    case VK_DOWN:
    case VK_HOME:
    case VK_END:
    case VK_NEXT:
    case VK_PRIOR:
        _AdjustCaretPosition((UINT)wParam, _GetKeyModifiers());
        break;

    case VK_INSERT:
        {
            CPoint pt;
            GetCaretPos(&pt);
            HideCaret();
            ToggleFlag(_dwState, BES_OVERWRITE);
            CreateSolidCaret(IsFlagSet(_dwState, BES_OVERWRITE) ? _tm.tmAveCharWidth : GetSystemMetrics(SM_CXBORDER), _cyRow);
            SetCaretPos(pt.x, pt.y);
            ShowCaret();
        }
        break;

    case VK_DELETE:
    case VK_BACK:
        _DeleteText((UINT)wParam);
        _RecalcLayout();
        // TODO: Optimize the invalidation
        InvalidateRect(NULL, FALSE);
        break;

    }
    return 0;
}

BOOL CBinaryEdit::_IsValidCharacter(WCHAR wc, __out BYTE *pb)
{
    BOOL fValid = FALSE;
    if (IsFlagSet(_dwState, BES_CHARGRIDFOCUS) && wc >= L' ')
    {
        *pb = (BYTE)wc;
        fValid = TRUE;
    }
    else if (IsFlagClear(_dwState, BES_CHARGRIDFOCUS))
    {
        wc = (WCHAR)CharLower((PWSTR)wc);
        if (wc >= L'0' && wc <= L'9')
        {
            *pb = (BYTE)(wc - L'0');
            fValid = TRUE;
        }
        else if (wc >= L'a' && wc <= L'f')
        {
            *pb = (BYTE)(wc - L'a' + 10);
            fValid = TRUE;
        }
    }
    return fValid;
}

BOOL CBinaryEdit::_HasSelection()
{
    return _iSelStart != _iSelEnd;
}

size_t CBinaryEdit::_GetDataActionIndex()
{
    size_t iIndex;
    if (_HasSelection())
    {
        iIndex = (_iSelStart < _iSelEnd) ? _iSelStart : _iSelEnd;
    }
    else
    {
        iIndex = _iCaretPos;
    }
    return iIndex;
}

LRESULT CBinaryEdit::_OnChar(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    WCHAR wc = (WCHAR)wParam;
    BYTE b;
    if (_IsValidCharacter(wc, &b))
    {
        BOOL fOverwrite = IsFlagSet(_dwState, BES_OVERWRITE) && !_HasSelection();
        BYTE bNew;
        if (IsFlagClear(_dwState, BES_CHARGRIDFOCUS))
        {
            bNew = (_iCaretPos < _rgData.GetCount()) ? (HIBYTE(_rgData.GetAt(_iCaretPos))) : 0;
            if (IsFlagClear(_dwState, BES_FOCUSONLOWNIBBLE))
            {
                bNew = (b << 4) | (fOverwrite ? (bNew & 0x0f) : 0);
            }
            else
            {
                fOverwrite = TRUE;
                bNew = (bNew & 0xf0) | b;
            }
        }
        else
        {
            bNew = b;
        }

        WORD wNew = MAKEWORD(TRUE, bNew);

        // Now figure out where the data insertion/replacement will happen
        size_t iAction = _GetDataActionIndex();
        size_t cwRemove;
        if (_HasSelection())
        {
            cwRemove = (_iSelStart < _iSelEnd) ? (_iSelEnd - _iSelStart) : (_iSelStart - _iSelEnd);
        }
        else
        {
            cwRemove = fOverwrite ? 1 : 0;
        }

        DWORD scpFlags = SCP_ENSUREVISIBLE;
        size_t iCaretPos = iAction;
        if (_HasSelection())
        {
            scpFlags |= SCP_LOWNIBBLE;
        }
        else
        {
            if (IsFlagSet(_dwState, BES_FOCUSONLOWNIBBLE))
            {
                iCaretPos++;
            }
            else
            {
                scpFlags |= SCP_LOWNIBBLE;
            }
        }

        _ModifyData(iAction, cwRemove, &wNew, 1);
        _SetCaretPos(iCaretPos, scpFlags);
        _RecalcLayout();
        // TODO: Optimize the invalidation
        InvalidateRect(NULL, FALSE);
    }
    else
    {
        if (wParam != VK_BACK)
        {
            MessageBeep(MB_OK);
        }
    }
    return 0;
}

LRESULT CBinaryEdit::_OnMenuSelect(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (_uiTBCtrlIdTrack == IDI_HEXCOLUMNSIZE)
    {
        _SetHexCellType((HEXCELLTYPE)LOWORD(wParam));
    }
    else
    {
        _SetHexCellDividerType((HEXCELLDIVIDERTYPE)LOWORD(wParam));
    }
    return 0;
}

LRESULT CBinaryEdit::_OnEnterIdle(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    if (wParam == MSGF_MENU)
    {
        UINT cItems = GetMenuItemCount(_hmnuTrack);
        BOOL fFound = FALSE;
        for (UINT i = 0; i < cItems && !fFound; i++)
        {
            if (GetMenuState(_hmnuTrack, i, MF_BYPOSITION) & MF_HILITE)
            {
                fFound = TRUE;
            }
        }
        if (!fFound)
        {
            if (_uiTBCtrlIdTrack == IDI_HEXCOLUMNSIZE)
            {
                _SetHexCellType((HEXCELLTYPE)_uiTrack);
            }
            else
            {
                _SetHexCellDividerType((HEXCELLDIVIDERTYPE)_uiTrack);
            }
        }
    }
    return 0;
}

BOOL CBinaryEdit::_SetHexCellType(HEXCELLTYPE hct)
{
    BOOL fRet = FALSE;
    if (hct > HCT_INVALID && hct < HCT_MAX && hct != _hct)
    {
        _hct = hct;
        switch (hct)
        {
        case HCT_BYTE:
            _cMinHexCols = 4;
            _cbHexCell = sizeof(BYTE);
            break;

        case HCT_WORD:
            _cMinHexCols = 2;
            _cbHexCell = sizeof(WORD);
            break;

        case HCT_DWORD:
            _cMinHexCols = 1;
            _cbHexCell = sizeof(DWORD);
            break;

        case HCT_QWORD:
            _cMinHexCols = 1;
            _cbHexCell = sizeof(DWORDLONG);
            break;

        }
        _RecalcLayout();
        InvalidateRect(NULL, FALSE);
        fRet = TRUE;
    }
    return fRet;
}

LRESULT CBinaryEdit::_OnSetHexCellType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    HEXCELLTYPE hct = _hct;
    if (!_SetHexCellType((HEXCELLTYPE)lParam))
    {
        hct = HCT_INVALID;
    }
    return hct;
}

LRESULT CBinaryEdit::_OnGetHexCellType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return _hct;
}

BOOL CBinaryEdit::_SetHexCellDividerType(HEXCELLDIVIDERTYPE hcdt)
{
    BOOL fRet = FALSE;
    if (hcdt > HCDT_INVALID && hcdt < HCDT_MAX && hcdt != _hcdt)
    {
        _hcdt = hcdt;
        InvalidateRect(NULL, FALSE);
        fRet = TRUE;
    }
    return fRet;
}

LRESULT CBinaryEdit::_OnSetHexCellDividerType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    HEXCELLDIVIDERTYPE hcdt = _hcdt;
    if (!_SetHexCellDividerType((HEXCELLDIVIDERTYPE)lParam))
    {
        hcdt = HCDT_INVALID;
    }
    return hcdt;
}

LRESULT CBinaryEdit::_OnGetHexCellDividerType(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return _hcdt;
}

LRESULT CBinaryEdit::_OnSetAlternateRowColor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    BOOL fAlternateRowColor = _fAlternateRowColor;
    _fAlternateRowColor = (BOOL)lParam;
    if (IsFlagSet(GetWindowLong(GWL_STYLE), BES_TOOLBAR))
    {
        _UpdateToolbarButtons();
    }
    return fAlternateRowColor;
}

LRESULT CBinaryEdit::_OnGetAlternateRowColor(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return _fAlternateRowColor;
}

LRESULT CBinaryEdit::_OnSetData(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    LRESULT lRet = 0;
    if (_rgData.SetCount(wParam))
    {
        WORD *pwDst = _rgData.GetData();
        const BYTE *pbSrc = (BYTE *)lParam;
        for (UINT i = 0; i < wParam; i++)
        {
            *pwDst++ = MAKEWORD(0, *pbSrc++);
        }
        _RecalcLayout();
        InvalidateRect(NULL, FALSE);
        lRet = 1;
    }
    return lRet;
}

LRESULT CBinaryEdit::_OnGetDataSize(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    return _rgData.GetCount();
}

LRESULT CBinaryEdit::_OnGetData(UINT uiMsg, WPARAM wParam, LPARAM lParam, BOOL &fHandled)
{
    LRESULT lRet = 0;
    if (wParam >= _rgData.GetCount())
    {
        const WORD *pwSrc = _rgData.GetData();
        BYTE *pbDst = (BYTE *)lParam;
        for (UINT i = 0; i < _rgData.GetCount(); i++)
        {
            *pbDst++ = HIBYTE(*pwSrc++);
        }
        lRet = _rgData.GetCount();
    }
    return lRet;
}

LRESULT CBinaryEdit::_OnAlternateRowColor(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL &fHandled)
{
    _fAlternateRowColor = !_fAlternateRowColor;
    InvalidateRect(NULL, FALSE);
    return 0;
}

LRESULT CBinaryEdit::_OnTBNDropDown(int nId, NMHDR *pnmhdr, BOOL &fHandled)
{
    NMTOOLBAR *pnmtb = (NMTOOLBAR *)pnmhdr;
    static const struct TBTRACKMAP
    {
        UINT uiTBCtrlId;
        UINT uiMenuId;
        UINT uiMenuLow;
        UINT uiMenuHigh;
    }
    c_rgTBTrackMap[] =
    {
        { IDI_HEXCOLUMNSIZE, IDM_BINARYEDIT_HEXCELLTYPE, HCT_BYTE, HCT_QWORD },
        { IDI_COLUMNDIVIDERS, IDM_BINARYEDIT_HEXCELLDIVIDERTYPE, HCDT_NONE, HCDT_AUTO },
    };

    const TBTRACKMAP *ptbtm = NULL;
    for (UINT iMap = 0; iMap < ARRAYSIZE(c_rgTBTrackMap) && !ptbtm; iMap++)
    {
        if (pnmtb->iItem == c_rgTBTrackMap[iMap].uiTBCtrlId)
        {
            ptbtm = &c_rgTBTrackMap[iMap];
        }
    }

    if (ptbtm)
    {
        if (SUCCEEDED(LoadPopupMenu(_AtlBaseModule.GetModuleInstance(), ptbtm->uiMenuId, &_hmnuTrack)))
        {
            CheckMenuRadioItem(_hmnuTrack, ptbtm->uiMenuLow, ptbtm->uiMenuHigh, (pnmtb->iItem == IDI_HEXCOLUMNSIZE ? _hct : _hcdt), MF_BYCOMMAND);

            _uiTBCtrlIdTrack = pnmtb->iItem;
            _uiTrack = (pnmtb->iItem == IDI_HEXCOLUMNSIZE ? _hct : _hcdt);

            TPMPARAMS tpmp = {sizeof(tpmp)};
            tpmp.rcExclude = pnmtb->rcButton;
            _wndToolbar.ClientToScreen(&tpmp.rcExclude);

            UINT uiState = (UINT)_wndToolbar.SendMessage(TB_GETSTATE, pnmtb->iItem, 0);
            _wndToolbar.SendMessage(TB_SETSTATE, pnmtb->iItem, uiState | TBSTATE_PRESSED);
            UINT uiSel = TrackPopupMenuEx(_hmnuTrack, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_HORIZONTAL | TPM_RETURNCMD, tpmp.rcExclude.left, tpmp.rcExclude.bottom, m_hWnd, &tpmp);
            if (uiSel == 0 && _uiTrack != (pnmtb->iItem == IDI_HEXCOLUMNSIZE ? _hct : _hcdt))
            {
                if (_uiTBCtrlIdTrack == IDI_HEXCOLUMNSIZE)
                {
                    _SetHexCellType((HEXCELLTYPE)_uiTrack);
                }
                else
                {
                    _hcdt = (HEXCELLDIVIDERTYPE)_uiTrack;
                    InvalidateRect(NULL, FALSE);
                }
            }
            _wndToolbar.SendMessage(TB_SETSTATE, pnmtb->iItem, uiState);
            DestroyMenu(_hmnuTrack);
            _hmnuTrack = NULL;
        }
    }
    return TBDDRET_DEFAULT;
}
